#include <stdio.h>
#include <iostream>
#include <stdlib.h>

//#include <GL/gl.h> <-- included in glut.h
//#include <GL/glu.h> <-- included in glut.h

#include <GL/glew.h>
#include <GL/glut.h>

#include <Cg/cg.h>
#include <Cg/cgGL.h>

using namespace std;

static GLint width  = 2;
static GLint height = 2;

// the texture to hold data on the GPU
GLuint texture;

// handle to frame buffer object
GLuint fb;

// arrays on the CPU to hold the data and result of GPU calucualtion
GLfloat* dataArr;
GLfloat* resultArr;

// some Cg variables
CGcontext cgContext;
CGprogram cgProgram;
CGprofile cgFragmentProfile;
CGparameter textureHandle = NULL;

//static GLenum textarget = GL_TEXTURE_2D;
static GLenum textarget = GL_TEXTURE_RECTANGLE_ARB;
//static GLenum texinternalformat = GL_RGBA32F_ARB;
static GLenum texinternalformat = GL_RGBA_FLOAT32_ATI;

// prototypes
void createCPUdata();
void initGLUT(int argc, char** argv);
void initGLEW();
void set1to1Mapping();  // sets the projection for 1:1 mapping
void initGPUTextures(); // set up texturs on gpu
void initCgProg();      // initialize the Cg shader program
void copyTexturesToGPU(); // copy CPU data to gpu texture
void printResults();
void render();
void readBacktoCPU();
void initFBO(void);


int main(int argc, char **argv) 
{
   createCPUdata();

   initGLUT(argc,argv);
   initGLEW();
   
   set1to1Mapping();
   initFBO();
      
   initGPUTextures(); // generates textures and associates
                      //  it with a texture name array
   copyTexturesToGPU();
   
   //readBacktoCPU();
   //printResults();

   initCgProg();
   render();
   
   readBacktoCPU();
   
   printResults();
   //glutDisplayFunc(&render);
   //glutIdleFunc(&render);
   //glutReshapeFunc(&render);

   //glutMainLoop();

   //sleep(60);
   // clean up
   free(dataArr);
   free(resultArr);
   glDeleteTextures (1,&texture);
   cgDestroyContext(cgContext);

   return 0;
}




void createCPUdata()
{
   cout << "-Creating CPU data ...." << flush;

   dataArr = (GLfloat*)malloc(height*width*4*sizeof(GLfloat));
   resultArr = (GLfloat*)malloc(height*width*4*sizeof(GLfloat));

   int index = 0;
   GLfloat colour = 0.0;
   for (int i=0;i<height; i++) {
      for (int j=0;j<width;j++) {
	 index = 4*((height*i)+j);
	 dataArr[index]   = colour + 0.1;
	 dataArr[index+1] = colour + 1.1;
	 dataArr[index+2] = colour + 2.1;
	 dataArr[index+3] = colour + 3.1;
      }
   }
   
   int index2 = 0;
   for (int i=0;i<height; i++) {
      for (int j=0;j<width;j++) {
	 index2 = 4*((height*i)+j);
	 resultArr[index2] = colour;
	 resultArr[index2+1] = colour;
	 resultArr[index2+2] = colour;
	 resultArr[index2+3] = colour;
      }
   }
   

   
   cout << "done" << endl;

   // declare texture size, the actual data will be a vector 
   // of size texSize*texSize*4
   // create test data and fill arbitrarily on CPU
   //
   //for (int i=0; i<texSize*texSize*4; i++)
   //   dataArr[i] = i+1.0;
}

void initGLUT(int argc, char** argv)
{
   glutInit (&argc, argv);
   //glutInitWindowSize(width,height);
   //glutInitWindowPosition(0,0);
   //glClearColor(0.0,0.0,0.0,0.0);
   //glClearDepth(1.0);
   //glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);   
   glutCreateWindow("TEST1");
   
   
   //glDepthFunc(GL_LESS);
   //glEnable(GL_DEPTH_TEST);
   //glShadeModel(GL_SMOOTH);
}

void initGLEW()
{
   glewInit();
}


void set1to1Mapping()
{
   // viewport transform for 1:1 pixel=texel=data mapping
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D(0.0,width,0.0,height); // (left,right,bottom,top)
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glViewport(0,0,width,height);
}


void initGPUTextures()
{
   // create texture
   glGenTextures (1, &texture);
   glBindTexture(textarget,texture);
   
   // set texture parameters 
   // this assumes the textures are already generated and bound
   // with glGenTextures()  and ...
   // glBindTexture()
   glTexParameteri(textarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexParameteri(textarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexParameteri(textarget, GL_TEXTURE_WRAP_S, GL_CLAMP);
   glTexParameteri(textarget, GL_TEXTURE_WRAP_T, GL_CLAMP);

   // define texture with floating point format 
   // note the last argument being 0 creates the texture but 
   // does not copy data to it
   glTexImage2D(textarget,0,texinternalformat,width,
		height,0,GL_RGBA,GL_FLOAT,0);

   // glEnable(GL_TEXTURE_RECTANGLE_ARB);
   
   return;
}




void initCgProg()
{
   // set up Cg

   // create the context - this is a container for cg programs
   cgContext = cgCreateContext();
   if (cgContext == 0) {
    cerr << "Cg Context creation failure" << endl;
    exit(-1);
   }

   cgFragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);
   if (cgFragmentProfile == CG_PROFILE_UNKNOWN) {
    cerr << "Invalid Fragment Program profile" << endl;
    exit(-1);
   }
   cgGLSetOptimalOptions(cgFragmentProfile);

   const char * programSource = "kernels/ident.frag";

   // create fragment program
   cgProgram  = cgCreateProgramFromFile(
		cgContext, // the contex where we want the shader added
		CG_SOURCE, // array of bytes containing the cg source
		"kernels/ident.frag", // the source file (char *)
		cgFragmentProfile, // the profile
		"fp",            // the entry function
		NULL);           // compiler options
   if (cgProgram == 0) {
    CGerror err = cgGetError();
    cerr << cgGetErrorString(err) << endl;
    exit(-1);
   }
   // load program
   cgGLLoadProgram(cgProgram);
   
   // get parameter handles by name
   // gets the named paramter "data" from the Cg fragment shader program
   // and stores it as a CGparamter called textureHandle
   textureHandle =  cgGetNamedParameter(cgProgram, "data");
   // we later use this handle to set the "data" texture to the openGL 
   // texture set up in initGPUtextures() with the texture array name 'texture'
}


void copyTexturesToGPU()
{
   // transfer data to texture
   
   //cgGLDisableProfile(cgFragmentProfile);
   //glDisable(GL_TEXTURE_RECTANGLE_ARB);
   //   glEnable(GL_TEXTURE_RECTANGLE_ARB);
   glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
                             GL_COLOR_ATTACHMENT0_EXT,
                             textarget,
			     texture,
			     0);
         
   glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT); 
   glRasterPos2i(0,0);    // reset the raster postion to (top left?)
   glDrawPixels(width,    // dimentions of the pixel rectangle to be
		height,   // written to the frame buffer
		GL_RGBA,  // format of the pixel data
		GL_FLOAT, // data type for pixels
		dataArr); // pointer to the pixel data

   //glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
   //                          GL_COLOR_ATTACHMENT0_EXT,
   //                          textarget,
   // 			     0,
   // 			     0);      
   //glEnable(GL_TEXTURE_RECTANGLE_ARB);
   //cgGLEnableProfile(cgFragmentProfile);
}


void render()
{
   //   setTextureAsRenderTarget();

   // bind fragment program (in this case set up to be ident.frag)
   cgGLBindProgram(cgProgram);
   // setting Input arrays/textures
   cgGLSetTextureParameter(textureHandle,texture);
   cgGLEnableTextureParameter(textureHandle);
   // enable fragment profile
   cgGLEnableProfile(cgFragmentProfile);
   
   // make quad filled to hit every pixel/texel
   glPolygonMode(GL_FRONT,GL_FILL);
   // render quad
   glBegin(GL_QUADS);
   glTexCoord2f(0.0, 0.0);
   glVertex2f(0.0, 0.0);
   glTexCoord2f(width, 0.0);
   glVertex2f(width, 0.0);
   glTexCoord2f(width, height);
   glVertex2f(width, height);
   glTexCoord2f(0.0, height);
   glVertex2f(0.0, height);     
   glEnd();


   cgGLDisableProfile(cgFragmentProfile);

   
}


void readBacktoCPU()
{
   // and read back                             
   glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);   
   glReadPixels(0, 0, width, height,GL_RGBA,GL_FLOAT,resultArr);        
}


void printResults()
{
   cout << "Data before roundtrip:" << endl;
   int index;
   for (int i=0;i<height; i++) {
      for (int j=0;j<width;j++) {
	 index = 4*((height*i)+j);
	 cout << dataArr[index] << " ";
	 cout << dataArr[index+1] << " ";
	 cout << dataArr[index+2] << " ";
	 cout << dataArr[index+3] << endl;
      }
   }
   
  
   cout << "Data after render: " << endl;
   index=0;
   for (int i=0;i<height; i++) {
      for (int j=0;j<width;j++) {
	 index = 4*((height*i)+j);
	 cout << resultArr[index] << " ";
	 cout << resultArr[index+1] << " ";
	 cout << resultArr[index+2] << " ";
	 cout << resultArr[index+3] << endl;
      }
   }
}



void initFBO(void)
{
   // create FB0 (off-screen frame buffer)
   // note that binding FBO to number 0 will restore
   // the window-system specific framebuffer at any time.
   glGenFramebuffersEXT(1, &fb);
   // bind offscreen buffer
   glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fb);
   return;
}



